/*
 * textillate.js
 * http://jschr.github.com/textillate
 * MIT licensed
 *
 * Copyright (C) 2012-2013 Jordan Schroter
 */
"use strict";
(function ($) {
    
    function isInEffect(effect) {
        return /In/.test(effect) || $.inArray(effect, $.fn.textillate.defaults.inEffects) >= 0;
    }
    
    function isOutEffect(effect) {
        return /Out/.test(effect) || $.inArray(effect, $.fn.textillate.defaults.outEffects) >= 0;
    }
    
    
    function stringToBoolean(str) {
        if (str !== "true" && str !== "false") { return str; }
        return (str === "true");
    }
    
    // custom get data api method
    function getData(node) {
        var attrs = node.attributes || [];
        var data = {};
        
        if (!attrs.length) { return data; }
        
        $.each(attrs, function (i, attr) {
            var nodeName = attr.nodeName.replace(/delayscale/, 'delayScale');
            if (/^data-in-*/.test(nodeName)) {
                data.in = data.in || {};
                data.in[nodeName.replace(/data-in-/, '')] = stringToBoolean(attr.nodeValue);
            } else if (/^data-out-*/.test(nodeName)) {
                data.out = data.out || {};
                data.out[nodeName.replace(/data-out-/, '')] = stringToBoolean(attr.nodeValue);
            } else if (/^data-*/.test(nodeName)) {
                data[nodeName.replace(/data-/, '')] = stringToBoolean(attr.nodeValue);
            }
        });
        
        return data;
    }
    
    function shuffle(o) {
        for (var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x) { };
        return o;
    }
    
    function animate($t, effect, cb) {
        $t.addClass('animated ' + effect)
      .css('visibility', 'visible')
      .show();
        
        $t.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
            $t.removeClass('animated ' + effect);
            cb && cb();
        });
    }
    
    function animateTokens($tokens, options, cb) {
        var that = this
      , count = $tokens.length;
        
        if (!count) {
            cb && cb();
            return;
        }
        
        if (options.shuffle) { $tokens = shuffle($tokens); }
        if (options.reverse) { $tokens = $tokens.toArray().reverse(); }
        
        $.each($tokens, function (i, t) {
            var $token = $(t);
            
            function complete() {
                if (isInEffect(options.effect)) {
                    $token.css('visibility', 'visible');
                } else if (isOutEffect(options.effect)) {
                    $token.css('visibility', 'hidden');
                }
                count -= 1;
                if (!count && cb) cb();
            }
            
            var delay = options.sync ? options.delay : options.delay * i * options.delayScale;
            
            $token.text() ?
        setTimeout(function () { animate($token, options.effect, complete) }, delay) :
        complete();
        });
    };
    
    var Textillate = function (element, options) {
        var base = this
      , $element = $(element);
        
        base.init = function () {
            base.$texts = $element.find(options.selector);
            
            if (!base.$texts.length) {
                base.$texts = $('<ul class="texts"><li>' + $element.html() + '</li></ul>');
                $element.html(base.$texts);
            }
            
            base.$texts.hide();
            
            base.$current = $('<span>')
        .html(base.$texts.find(':first-child').html())
        .prependTo($element);
            
            if (isInEffect(options.in.effect)) {
                base.$current.css('visibility', 'hidden');
            } else if (isOutEffect(options.out.effect)) {
                base.$current.css('visibility', 'visible');
            }
            
            base.setOptions(options);
            
            base.timeoutRun = null;
            
            setTimeout(function () {
                base.options.autoStart && base.start();
            }, base.options.initialDelay)
        };
        
        base.setOptions = function (options) {
            base.options = options;
        };
        
        base.triggerEvent = function (name) {
            var e = $.Event(name + '.tlt');
            $element.trigger(e, base);
            return e;
        };
        
        base.in = function (index, cb) {
            index = index || 0;
            
            var $elem = base.$texts.find(':nth-child(' + ((index || 0) + 1) + ')')
        , options = $.extend(true, {}, base.options, $elem.length ? getData($elem[0]) : {})
        , $tokens;
            
            $elem.addClass('current');
            
            base.triggerEvent('inAnimationBegin');
            
            base.$current
        .html($elem.html())
        .lettering('words');
            
            // split words to individual characters if token type is set to 'char'
            if (base.options.type == "char") {
                base.$current.find('[class^="word"]')
            .css({
                    'display': 'inline-block',
                    // fix for poor ios performance
                    '-webkit-transform': 'translate3d(0,0,0)',
                    '-moz-transform': 'translate3d(0,0,0)',
                    '-o-transform': 'translate3d(0,0,0)',
                    'transform': 'translate3d(0,0,0)'
                })
            .each(function () { $(this).lettering() });
            }
            
            $tokens = base.$current
        .find('[class^="' + base.options.type + '"]')
        .css('display', 'inline-block');
            
            if (isInEffect(options.in.effect)) {
                $tokens.css('visibility', 'hidden');
            } else if (isOutEffect(options.in.effect)) {
                $tokens.css('visibility', 'visible');
            }
            
            base.currentIndex = index;
            
            animateTokens($tokens, options.in, function () {
                base.triggerEvent('inAnimationEnd');
                if (options.in.callback) options.in.callback();
                if (cb) cb(base);
            });
        };
        
        base.out = function (cb) {
            var $elem = base.$texts.find(':nth-child(' + ((base.currentIndex || 0) + 1) + ')')
        , $tokens = base.$current.find('[class^="' + base.options.type + '"]')
        , options = $.extend(true, {}, base.options, $elem.length ? getData($elem[0]) : {})
            
            base.triggerEvent('outAnimationBegin');
            
            animateTokens($tokens, options.out, function () {
                $elem.removeClass('current');
                base.triggerEvent('outAnimationEnd');
                if (options.out.callback) options.out.callback();
                if (cb) cb(base);
            });
        };
        
        base.start = function (index) {
            setTimeout(function () {
                base.triggerEvent('start');
                
                (function run(index) {
                    base.in(index, function () {
                        var length = base.$texts.children().length;
                        
                        index += 1;
                        
                        if (!base.options.loop && index >= length) {
                            if (base.options.callback) base.options.callback();
                            base.triggerEvent('end');
                        } else {
                            index = index % length;
                            
                            base.timeoutRun = setTimeout(function () {
                                base.out(function () {
                                    run(index)
                                });
                            }, base.options.minDisplayTime);
                        }
                    });
                }(index || 0));
            }, base.options.initialDelay);
        };
        
        base.stop = function () {
            if (base.timeoutRun) {
                clearInterval(base.timeoutRun);
                base.timeoutRun = null;
            }
        };
        
        base.init();
    }
    
    $.fn.textillate = function (settings, args) {
        return this.each(function () {
            var $this = $(this)
        , data = $this.data('textillate')
        , options = $.extend(true, {}, $.fn.textillate.defaults, getData(this), typeof settings == 'object' && settings);
            
            if (!data) {
                $this.data('textillate', (data = new Textillate(this, options)));
            } else if (typeof settings == 'string') {
                data[settings].apply(data, [].concat(args));
            } else {
                data.setOptions.call(data, options);
            }
        })
    };
    
    $.fn.textillate.defaults = {
        selector: '.texts',
        loop: false,
        minDisplayTime: 2000,
        initialDelay: 0,
        in: {
            effect: 'fadeInLeftBig',
            delayScale: 1.5,
            delay: 50,
            sync: false,
            reverse: false,
            shuffle: false,
            callback: function () { }
        },
        out: {
            effect: 'hinge',
            delayScale: 1.5,
            delay: 50,
            sync: false,
            reverse: false,
            shuffle: false,
            callback: function () { }
        },
        autoStart: true,
        inEffects: [],
        outEffects: ['hinge'],
        callback: function () { },
        type: 'char'
    };

}(jQuery));
